#import "CoreDataLogger.h"
#import "LogEntry.h"

@interface CoreDataLogger (PrivateAPI)
- (void)validateLogDirectory;
- (void)createManagedObjectContext;
@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@implementation CoreDataLogger

- (id)initWithLogDirectory:(NSString *)aLogDirectory
{
	if ((self = [super init]))
	{
		logDirectory = [aLogDirectory copy];
		
		[self validateLogDirectory];
		[self createManagedObjectContext];
	}
	return self;
}

- (void)dealloc
{
	[logDirectory release];
	
	[logEntryEntity release];
	[managedObjectContext release];
	[persistentStoreCoordinator release];
	[managedObjectModel release];
	
    [super dealloc];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Private API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)validateLogDirectory
{
	// Validate log directory exists or create the directory.
	
	BOOL isDirectory;
	if ([[NSFileManager defaultManager] fileExistsAtPath:logDirectory isDirectory:&isDirectory])
	{
		if (!isDirectory)
		{
			NSLog(@"%@: %@ - logDirectory(%@) is a file!", [self class], THIS_METHOD, logDirectory);
			
			[logDirectory release];
			logDirectory = nil;
		}
	}
	else
	{
		NSError *error = nil;
		
		BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:logDirectory
		                                        withIntermediateDirectories:YES
		                                                         attributes:nil
		                                                              error:&error];
		if (!result)
		{
			NSLog(@"%@: %@ - Unable to create logDirectory(%@) due to error: %@",
				  [self class], THIS_METHOD, logDirectory, error);
			
			[logDirectory release];
			logDirectory = nil;
		}
	}
}

- (NSString *)logFilePath
{
	return [logDirectory stringByAppendingPathComponent:@"Log.sqlite"];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Core Data
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (NSManagedObjectModel *)managedObjectModel
{
	if (managedObjectModel)
	{
		return managedObjectModel;
	}
	
	NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Log" withExtension:@"momd"];
	managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
	return managedObjectModel;
}

- (BOOL)addPersistentStore:(NSError **)errorPtr
{
	if (logDirectory == nil)
	{
		if (errorPtr)
		{
			NSString *errMsg = @"Invalid logDirectory";
			NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
			
			*errorPtr = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:userInfo];
		}
		return NO;
	}
	
	NSURL *url = [NSURL fileURLWithPath:[self logFilePath]];
	
	NSPersistentStore *result = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
	                                                                     configuration:nil
	                                                                               URL:url
	                                                                           options:nil
	                                                                             error:errorPtr];
	return (result != nil);
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
	if (persistentStoreCoordinator)
	{
		return persistentStoreCoordinator;
	}
	
	NSManagedObjectModel *mom = [self managedObjectModel];
	if (!mom)
	{
		NSLog(@"%@: %@ - No model to generate a store from", [self class], THIS_FILE);
		return nil;
    }
	
	if (logDirectory == nil)
	{
		return nil;
	}
	
	persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
	
	NSError *error = nil;
	if (![self addPersistentStore:&error])
	{
		NSLog(@"%@: %@ - Error creating persistent store: %@", [self class], THIS_FILE, error);
		
		[persistentStoreCoordinator release];
		persistentStoreCoordinator = nil;
	}
	
	return persistentStoreCoordinator;
}

- (void)createManagedObjectContext
{
	NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
	if (coordinator)
	{
		if (managedObjectContext == nil)
		{
			managedObjectContext = [[NSManagedObjectContext alloc] init];
			[managedObjectContext setPersistentStoreCoordinator:coordinator];
			[managedObjectContext setMergePolicy:NSOverwriteMergePolicy];
		}
		
		if (logEntryEntity == nil)
		{
			logEntryEntity = [[NSEntityDescription entityForName:@"LogEntry"
			                              inManagedObjectContext:managedObjectContext] retain];
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Public API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)clearLog
{
	dispatch_block_t block = ^{
		
		if (managedObjectContext == nil)
		{
			return;
		}
		
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		
		NSError *error = nil;
		
		[managedObjectContext reset];
		[persistentStoreCoordinator lock];
		
		NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] lastObject];
		
		if (![persistentStoreCoordinator removePersistentStore:store error:&error])
		{
			NSLog(@"%@: %@ - Error removing persistent store: %@", [self class], THIS_METHOD, error);
		} 
		
		NSString *logFilePath = [self logFilePath];
		
		if ([[NSFileManager defaultManager] fileExistsAtPath:logFilePath])
		{
			if (![[NSFileManager defaultManager] removeItemAtPath:logFilePath error:&error])
			{
				NSLog(@"%@: %@ - Error deleting log file: %@", [self class], THIS_METHOD, error);
			}
		}
		
		if (![self addPersistentStore:&error])
		{
			NSLog(@"%@: %@ - Error creating persistent store: %@", [self class], THIS_FILE, error);
		}
		
		[persistentStoreCoordinator unlock];
		
		[pool drain];
	};
	
	if (dispatch_get_current_queue() == loggerQueue)
		block();
	else
		dispatch_async(loggerQueue, block);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark DDLogger
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)db_log:(DDLogMessage *)logMessage
{
	if (managedObjectContext == nil)
	{
		return NO;
	}
	
	LogEntry *logEntry = [[NSManagedObject alloc] initWithEntity:logEntryEntity
	                              insertIntoManagedObjectContext:managedObjectContext];
	
	logEntry.context   = [NSNumber numberWithInt:logMessage->logContext];
	logEntry.level     = [NSNumber numberWithInt:logMessage->logFlag];
	logEntry.message   = logMessage->logMsg;
	logEntry.timestamp = logMessage->timestamp;
	
	[logEntry release];
	
	return YES;
}

- (void)saveContext
{
	if ([managedObjectContext hasChanges])
	{
		NSError *error = nil;
		if (![managedObjectContext save:&error])
		{
			NSLog(@"%@: Error saving: %@ %@", [self class], error, [error userInfo]);
			
			// Since the save failed, we are forced to dump the log entries.
			// If we don't we risk an ever growing managedObjectContext,
			// as the unsaved changes sit around in RAM until either saved or dumped.
			
			[managedObjectContext rollback];
		}
	}
}

- (void)deleteOldLogEntries:(BOOL)shouldSaveWhenDone
{
	if (maxAge <= 0.0)
	{
		// Deleting old log entries is disabled.
		// The superclass won't likely call us if this is the case, but we're being cautious.
		return;
	}
	
	NSEntityDescription *entity = [NSEntityDescription entityForName:@"LogEntry"
	                                          inManagedObjectContext:managedObjectContext];
	
	NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:(-1.0 * maxAge)];
	NSPredicate *predicate = [NSPredicate predicateWithFormat:@"timestamp < %@", maxDate];
	
	NSUInteger batchSize = (saveThreshold > 0) ? saveThreshold : 500;
	NSUInteger count = 0;
	
	NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
	[fetchRequest setEntity:entity];
	[fetchRequest setFetchBatchSize:batchSize];
	[fetchRequest setPredicate:predicate];
	
	NSArray *oldLogEntries = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
	
	for (LogEntry *logEntry in oldLogEntries)
	{
		[managedObjectContext deleteObject:logEntry];
		
		if (++count >= batchSize)
		{
			[self saveContext];
		}
	}
	
	if (shouldSaveWhenDone)
	{
		[self saveContext];
	}
}

- (void)db_save
{
	[self saveContext];
}

- (void)db_delete
{
	[self deleteOldLogEntries:YES];
}

- (void)db_saveAndDelete
{
	[self deleteOldLogEntries:NO];
	[self saveContext];
}

@end
